from ftplib import FTP

class MyFTP:
    def __init__(self, host="127.0.0.1", port=21, user="", passwd=""):
        self.ftp = FTP()
        self.connect(host=host, port=port)
        self.login(user=user, passwd=passwd)
        self.set_debuglevel(1)

    def connect(self, host='', port=0, timeout=None, source_address=None):
        '''
        连接到给定的主机和端口。默认端口号由 FTP 协议规范规定，为 21。偶尔才需要指定其他端口号。
        每个实例只应调用一次本函数，如果在创建实例时就传入了 host，则根本不应调用它。所有其他方法只能在建立连接后使用。
        可选参数 timeout 指定连接尝试的超时（以秒为单位）。如果没有传入 timeout，将使用全局默认超时设置。
        source_address 是一个 2 元组 (host, port)，套接字在连接前绑定它，作为其源地址。
        :param host:
        :param port:
        :param timeout:
        :param source_address:
        :return:
        '''
        self.ftp.connect(host=host, port=port, timeout=timeout, source_address=source_address)

    def set_debuglevel(self, val):
        '''
        设置实例的调试级别，它控制着调试信息的数量。默认值 0 不产生调试信息。值 1 产生中等数量的调试信息，
        通常每个请求产生一行。大于或等于 2 的值产生的调试信息最多，FTP 控制连接上发送和接收的每一行都将被记录下来。
        :param val:
        :return:
        '''
        self.ftp.set_debuglevel(val)

    def getwelcome(self):
        '''
        返回服务器发送的欢迎消息，作为连接开始的回复。（该消息有时包含与用户有关的免责声明或帮助信息。）
        :return:
        '''
        return self.ftp.getwelcome()

    def login(self, user, passwd):
        '''
        以 user 的身份登录。passwd 和 acct 是可选参数，默认为空字符串。如果没有指定 user，则默认为 'anonymous'。
        如果 user 为 'anonymous'，那么默认的 passwd 是 'anonymous@'。连接建立后，每个实例只应调用一次本函数；
        如果在创建实例时传入了 host 和 user，则完全不应该调用本函数。在客户端登录后，才允许执行大多数 FTP 命令。
        :param user:
        :param passwd:
        :return:
        '''
        self.ftp.login(user=user, passwd=passwd)

    def abort(self):
        '''
        中止正在进行的文件传输。本方法并不总是有效，但值得一试。
        :return:
        '''
        self.ftp.abort()

    def sendcmd(self, cmd):
        '''
        将一条简单的命令字符串发送到服务器，返回响应的字符串。
        引发一个 审计事件 ftplib.sendcmd，附带参数 self, cmd。
        :param cmd:
        :return:
        '''
        self.ftp.sendcmd(cmd=cmd)

    def voidcmd(self, cmd):
        '''
        将一条简单的命令字符串发送到服务器，并处理响应内容。如果收到的响应代码表示的是成功（代码范围 200--299），
        则不返回任何内容。否则将引发 error_reply。
        引发一个 审计事件 ftplib.sendcmd，附带参数 self, cmd。
        :return:
        '''
        self.ftp.voidcmd(cmd=cmd)

    def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
        '''
        以二进制传输模式下载文件。cmd 应为恰当的 RETR 命令：'RETR 文件名'。callback 函数会在收到每个数据块时调用，
        传入的参数是表示数据块的一个字节。为执行实际传输，创建了底层套接字对象，可选参数 blocksize 指定了读取该对象时的
        最大块大小（这也是传入 callback 的数据块的最大大小）。已经选择了合理的默认值。rest 的含义与 transfercmd()
        方法中的含义相同。
        :return:
        '''
        self.ftp.retrbinary(cmd=cmd, callback=callback, blocksize=blocksize, rest=rest)

    def retrlines(self, cmd, callback=None):
        '''
        按照初始化时的 encoding 参数指定的编码，获取文件或目录列表。cmd 应是恰当的 RETR 命令（参阅 retrbinary()），
        也可以是诸如 LIST 或 NLST 之类的命令（通常就只是字符串 'LIST'）。LIST 获取文件列表以及那些文件的信息。
        NLST 获取文件名称列表。callback 函数会在每一行都调用，参数就是包含一行的字符串，删除了尾部的 CRLF。
        默认的 callback 会把行打印到 sys.stdout。
        :return:
        '''
        self.ftp.retrlines(cmd=cmd, callback=callback)

    def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
        '''
        以二进制传输模式存储文件。 cmd 应为恰当的 STOR 命令: "STOR filename"。fp 是一个 文件对象 (以二进制模式打开)，
        将使用它的 read() 方法读取它，用于提供要存储的数据，直到遇到 EOF，读取时的块大小为 blocksize。
        参数 blocksize 的默认值为 8192。 可选参数 callback 是单参数函数，在每个数据块发送后都会以该数据块作为参数来
        调用它。rest 的含义与 transfercmd() 方法中的含义相同。
        在 3.2 版更改: 添加了 rest 参数。
        :return:
        '''
        self.ftp.storbinary(cmd=cmd, fp=fp, blocksize=blocksize, callback=callback, rest=rest)

    def storlines(self, cmd, fp, callback=None):
        '''
        以文本行模式存储文件。cmd 应为恰当的 STOR 命令 (请参阅 storbinary())。 fp 是一个 文件对象 (以二进制模式打开)，
        将使用它的 readline() 方法读取它的每一行，用于提供要存储的数据，直到遇到 EOF。 可选参数 callback 是单参数函数，
        在每行发送后都会以该行作为参数来调用它。
        :param cmd:
        :param fp:
        :param callback:
        :return:
        '''
        self.ftp.storlines(cmd=cmd, fp=fp, callback=callback)

    def transfercmd(self, cmd, rest=None):
        '''
        在 FTP 数据连接上开始传输数据。如果传输处于活动状态，传输命令由 cmd 指定，需发送 EPRT 或 PORT 命令，
        然后接受连接 (accept)。如果服务器是被动服务器，需发送 EPSV 或 PASV 命令，连接到服务器 (connect)，然后启动传输命令。
        两种方式都将返回用于连接的套接字。
        如果传入了可选参数 rest，则一条 REST 命令会被发送到服务器，并以 rest 作为参数。rest 通常表示请求文件中的字节偏移量，
        它告诉服务器重新开始发送文件的字节，从请求的偏移量处开始，跳过起始字节。但是请注意，transfercmd() 方法会将 rest 转换
        为字符串，但是不检查字符串的内容，转换用的编码是在初始化时指定的 encoding 参数。如果服务器无法识别 REST 命令，
        将引发 error_reply 异常。如果发生这种情况，只需不带 rest 参数调用 transfercmd()。
        :return:
        '''
        self.ftp.transfercmd(cmd=cmd, rest=rest)

    def ntransfercmd(self, cmd, rest=None):
        '''
        类似于 transfercmd()，但返回一个元组，包括数据连接和数据的预计大小。如果预计大小无法计算，则返回的预计大小为 None。
        cmd 和 rest 的含义与 transfercmd() 中的相同。
        :param cmd:
        :param rest:
        :return:
        '''
        self.ftp.ntransfercmd(cmd=cmd, rest=rest)

    def nlst(self,path=''):
        '''
        返回一个文件名列表，文件名由 NLST 命令返回。可选参数 argument 是待列出的目录（默认为当前服务器目录）。可以使用多个参数，将非标准选项传递给 NLST 命令。
        :return:
        '''
        return self.ftp.nlst(path)

    def mlsd(self, path=''):
        '''
        获取文件列表
        :param path: 要获取的路径可以为空，则为当前目录
        :return: 返回文件列表
        '''
        res = self.ftp.mlsd(path=path, facts=["type", "size", "perm"])
        fileList = []
        for item in res:
            file = {}
            file['name'] = item[0]
            file['type'] = item[1]['type']
            try:
                file['size'] = item[1]['sizd']
            except:
                file['size'] = item[1]['size']
            file['modify'] = item[1]['modify']

            if not (file['type'] == 'cdir' or file['type'] == 'pdir'):
                fileList.append(file)
        return fileList

    def set_pasv(self, val):
        '''
        设置被动模式：如果 val 为 true，则打开“被动”模式，否则禁用被动模式。默认下被动模式是打开的。
        :param val:
        :return:
        '''
        self.ftp.set_pasv(val=val)

    def deleteFile(self, fileName):
        '''
        删除文件
        :param fileName: 要删除的文件名
        :return: 成功返回字符串，失败返回空
        '''
        res = self.ftp.delete(filename=fileName)
        print(res)

    def mkd(self, pathname):
        '''
        创建目录
        :param pathname: 要创建的目录名称
        :return:
        '''
        self.ftp.mkd(pathname)

    def rmd(self, pathname):
        '''
        删除目录
        :param pathname: 要删除的目录名称
        :return:
        '''
        self.ftp.rmd(pathname)

    def size(self, fileName):
        '''
        取文件大小
        :param fileName: 文件名称
        :return:
        '''
        return self.ftp.size(filename=fileName)

    def quit(self):
        '''
        关闭连接
        :return:
        '''
        self.ftp.quit()

    def close(self):
        '''
        强制关闭连接
        :return:
        '''
        self.ftp.close()

    def rename(self, fromName, toName):
        '''
        文件重命名
        :param fromName: 旧文件名
        :param toName: 新文件名
        :return:
        '''
        self.ftp.rename(fromname=fromName, toname=toName)

    def cwd(self, path):
        '''
        设置当前目录
        :param path:
        :return:
        '''
        self.ftp.cwd(path)

    def pwd(self):
        '''
        取当前目录
        :return:
        '''
        return self.ftp.pwd()


# https://docs.python.org/zh-cn/3/library/ftplib.html#ftplib.FTP.transfercmd
if __name__ == '__main__':  # youcans, XUPT 2021
    MF = MyFTP()
    print(MF.nlst())



